/*
 *  AObjDoc.cpp
 *
 *
 *  Created by Peter on 23.02.12.
 *  Copyright 2012 __MyCompanyName__. All rights reserved.
 *
 */

#include "AObjDoc.h"
#include "AStream.h"
#include "PClassRegistry.h"

#include <memory> /*for std::auto_ptr*/

A_IMPLEMENT_CLASS( 'OBJD', AObjDoc, AObject );

AObjDoc::AObjDoc()
{
}

void AObjDoc::readFromStream( AStream& str, const AObjDoc& objd )
{
	readFromStream( str );
}

void AObjDoc::readFromStream( AStream& str )
{
	clear();

	// I'm assuming this is the version, could be something else
	UInt8 version;
	str.readPod( version );
	if( version != 0x04 )
		throw "Invalid AObjDoc version!";

	// Last saved date as a string,
	_lastSavedStr = str.readString();

	// Number of objects in the AObjDoc
	str.readPod( _numObjects );

	// The main object in the document (usually an ABin for bin files)
	str.readPod( _rootObject );

	if( _rootObject != _numObjects) {
		TR_TRACE (
			"Root object doesn't seem to be the last object in the AObjDoc. "
			"This is rather unusual."
		);
	}

	// Four bytes set to 0x49 ('IIII'). Avid ignores these, hence we do, too.
	// Seems to be an endianity marker?
	str.skip( 4 );

	// Last saved date as a unix timestamp (used as high word in a UID)
	str.readPod( _lastSaved );

	// 4 bytes of unknown data that changes every time the file is saved (low word in the UID)
	str.skip( 4 );

	str.readPod( _fileType );
	str.readPod( _creator );

	_creatorVersion = str.readString();

	// Reserved data
	str.skip( 16 );

	// For now, dump what we have already
	dump( std::clog );

	// Prevent our object vector from resizing its buffer all the time
	_objects.reserve( _numObjects );

#ifdef DEBUG
	// In debug builds, we keep track of how often each object was claimed by a
	// call to resolveRef(). That way we know if we are leaking objects.
	_numTimesClaimed.clear();
	_numTimesClaimed.reserve( _numObjects );

	for( int i = 0; i < _numObjects; ++i )
		_numTimesClaimed[ i ] = 0;
#endif

	TR_TRACE( "AObjDoc: Reading " << _numObjects << " objects..." );

	// Read in all the objects
	for( int i = 0; i < _numObjects; ++i )
	{
		// Read the object's PCID
		AObject::PCID pcid;
		str.readPod( pcid );

		// Read the chunk length
		UInt32 length;
		str.readPod( length );

		// If the object is unknown, skip the data and proceed with the next object
		if( !PClassRegistry::isClassRegistered( pcid ) )
		{
			TR_TRACE( "Unknown PCID '" << pcidToString( pcid ) << "', skipping..." );
			str.skip( length );

			// Add an empty object here so the indices for references don't get
			// all messed up
			_objects.push_back( new AObject );
			continue;
		}

		// Create an instance of the requested object
		std::auto_ptr< AObject > obj( PClassRegistry::createInstance( pcid ) );

		// See where we are in the stream before the object is read
		long pos1 = str.getPos();

		TR_TRACE (
			"Reading object #" << i + 1 /*Object references are 1-based I think*/
			<< ", type is " << obj->getClassName()
			<< ", PCID is '" << pcidToString( pcid ) << "'"
		);

		try {
			// Have the object read its data
			obj->readFromStream( str, *this );
		} catch ( const char* e ) {
			TR_TRACE (
				"Error reading object of type '" << pcidToString( pcid )
			    << "': " << e
			);
		}

		// See how much data was read by that object
		long bytesRead = static_cast< long >( str.getPos() ) - pos1;

		// If the object read more or less data than it should have according
		// to the length field, we log a warning an seek to the correct
		// position.
		// If you see this warning, either the reading code for the object is
		// buggy or incomplete, or your file is corrupt. Since all this is
		// non-authorized reverse-engineered stuff, chances are the former
		// is the case.
		if( bytesRead != length ) {
			TR_TRACE (
				"WARNING: Object of type " << obj->getClassName()
				<< " (PCID: '" << pcidToString( pcid ) << "') read " << bytesRead
				<< " bytes of data, should have read " << length
				<< " (difference: " << length-bytesRead << "). Compensating..."
			);
			str.seekRelative( length - bytesRead );
		}

		// Dump the object for now
		obj->dump( std::cout );

		// Add our object to the list
		_objects.push_back( obj.release() );
	}
}

void AObjDoc::writeToStream( AStream& str ) const
{
	// TODO: Implement
}

void AObjDoc::clear()
{
	// TODO: Implement
	// Question is: Do we want to free all objects? I.e. do we want to own them?
}

AObject* AObjDoc::getObjectByIdx( ObjectRef ref ) const
{
	if( ref < 0 || ref > _objects.size() )
		throw "Index out of range.";

	if( ref == 0 )
		return nullptr;

	AObject* obj = _objects[ ref - 1 ];

	if( !obj )
		throw "Object not found or NULL";

	return obj;
}

AObject* AObjDoc::getRootObject() const
{
	return getObjectByIdx( _rootObject );
}

#define TRACE_MEMBER( x ) "\t" #x ": " << x << std::endl

void AObjDoc::dump( std::ostream& str )
{
	str << "AObjDoc {" << std::endl;
	str <<     TRACE_MEMBER( _lastSavedStr );
	str <<     TRACE_MEMBER( _numObjects );
	str <<     TRACE_MEMBER( _rootObject );
	str <<     TRACE_MEMBER( _lastSaved );
	str <<     "\t_creator: '" << pcidToString( _creator ) << "'" << std::endl;
	str <<     "\t_fileType: '" << pcidToString( _fileType ) << "'" << std::endl;
	str <<     TRACE_MEMBER( _creatorVersion );
	str <<     "\t_objects[ " << _objects.size() << " ] {" << std::endl;

	str <<     "\t\t..." << std::endl;
	// for each object:
	// str << "\t" << object << std::endl;

	str <<     "\t}" << std::endl;
	str << "}" << std::endl;
}
